 /*******************************************************************************
  * Copyright (c) 2005, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * Brad Reynolds - bug 164268, 171616
  * Brad Reynolds - bug 147515
  *******************************************************************************/
 package org.eclipse.core.databinding.beans;

 import java.beans.BeanInfo ;
 import java.beans.IntrospectionException ;
 import java.beans.Introspector ;
 import java.beans.PropertyDescriptor ;

 import org.eclipse.core.databinding.BindingException;
 import org.eclipse.core.databinding.observable.IObservable;
 import org.eclipse.core.databinding.observable.Realm;
 import org.eclipse.core.databinding.observable.list.IObservableList;
 import org.eclipse.core.databinding.observable.map.IObservableMap;
 import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
 import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables;
 import org.eclipse.core.databinding.observable.set.IObservableSet;
 import org.eclipse.core.databinding.observable.value.IObservableValue;
 import org.eclipse.core.internal.databinding.internal.beans.BeanObservableListDecorator;
 import org.eclipse.core.internal.databinding.internal.beans.BeanObservableSetDecorator;
 import org.eclipse.core.internal.databinding.internal.beans.BeanObservableValueDecorator;
 import org.eclipse.core.internal.databinding.internal.beans.JavaBeanObservableList;
 import org.eclipse.core.internal.databinding.internal.beans.JavaBeanObservableMap;
 import org.eclipse.core.internal.databinding.internal.beans.JavaBeanObservableSet;
 import org.eclipse.core.internal.databinding.internal.beans.JavaBeanObservableValue;

 /**
  * A factory for creating observable objects of Java objects that conform to the
  * <a HREF="http://java.sun.com/products/javabeans/docs/spec.html">JavaBean
  * specification</a> for bound properties.
  *
  * @since 1.1
  *
  */
 final public class BeansObservables {

     /**
      *
      */
     public static final boolean DEBUG = true;

     /**
      * Returns an observable value in the default realm tracking the current
      * value of the named property of the given bean.
      *
      * @param bean
      * the object
      * @param propertyName
      * the name of the property
      * @return an observable value tracking the current value of the named
      * property of the given bean
      */
     public static IObservableValue observeValue(Object bean, String propertyName) {
         return observeValue(Realm.getDefault(), bean, propertyName);
     }

     /**
      * Returns an observable value in the given realm tracking the current value
      * of the named property of the given bean.
      *
      * @param realm
      * the realm
      * @param bean
      * the object
      * @param propertyName
      * the name of the property
      * @return an observable value tracking the current value of the named
      * property of the given bean
      */
     public static IObservableValue observeValue(Realm realm, Object bean,
             String propertyName) {
         PropertyDescriptor descriptor = getPropertyDescriptor(bean.getClass(),
                 propertyName);
         return new JavaBeanObservableValue(realm, bean, descriptor, null);
     }

     /**
      * Returns an observable map in the default realm tracking the current
      * values of the named property for the beans in the given set.
      *
      * @param domain
      * the set of bean objects
      * @param beanClass
      * the common base type of bean objects that may be in the set
      * @param propertyName
      * the name of the property
      * @return an observable map tracking the current values of the named
      * property for the beans in the given domain set
      */
     public static IObservableMap observeMap(IObservableSet domain,
             Class beanClass, String propertyName) {
         PropertyDescriptor descriptor = getPropertyDescriptor(beanClass,
                 propertyName);
         return new JavaBeanObservableMap(domain, descriptor);
     }

     private static PropertyDescriptor getPropertyDescriptor(Class beanClass,
             String propertyName) {
         BeanInfo beanInfo;
         try {
             beanInfo = Introspector.getBeanInfo(beanClass);
         } catch (IntrospectionException e) {
             // cannot introspect, give up
 return null;
         }
         PropertyDescriptor [] propertyDescriptors = beanInfo
                 .getPropertyDescriptors();
         for (int i = 0; i < propertyDescriptors.length; i++) {
             PropertyDescriptor descriptor = propertyDescriptors[i];
             if (descriptor.getName().equals(propertyName)) {
                 return descriptor;
             }
         }
         throw new BindingException(
                 "Could not find property with name " + propertyName + " in class " + beanClass); //$NON-NLS-1$ //$NON-NLS-2$
 }

     /**
      * Returns an array of observable maps in the default realm tracking the
      * current values of the named propertys for the beans in the given set.
      *
      * @param domain
      * the set of objects
      * @param beanClass
      * the common base type of objects that may be in the set
      * @param propertyNames
      * the array of property names
      * @return an array of observable maps tracking the current values of the
      * named propertys for the beans in the given domain set
      */
     public static IObservableMap[] observeMaps(IObservableSet domain,
             Class beanClass, String [] propertyNames) {
         IObservableMap[] result = new IObservableMap[propertyNames.length];
         for (int i = 0; i < propertyNames.length; i++) {
             result[i] = observeMap(domain, beanClass, propertyNames[i]);
         }
         return result;
     }

     /**
      * Returns an observable list in the given realm tracking the
      * collection-typed named property of the given bean object. The returned
      * list is mutable.
      *
      * @param realm
      * the realm
      * @param bean
      * the object
      * @param propertyName
      * the name of the collection-typed property
      * @return an observable list tracking the collection-typed named property
      * of the given bean object
      * @see #observeList(Realm, Object, String, Class)
      */
     public static IObservableList observeList(Realm realm, Object bean,
             String propertyName) {
         return observeList(realm, bean, propertyName, null);
     }

     /**
      * Returns an observable list in the given realm tracking the
      * collection-typed named property of the given bean object. The returned
      * list is mutable. When an item is added or removed the setter is invoked
      * for the list on the parent bean to provide notification to other
      * listeners via <code>PropertyChangeEvents</code>. This is done to
      * provide the same behavior as is expected from arrays as specified in the
      * bean spec in section 7.2.
      *
      * @param realm
      * the realm
      * @param bean
      * the bean object
      * @param propertyName
      * the name of the property
      * @param elementType
      * type of the elements in the list. If <code>null</code> and
      * the property is an array the type will be inferred. If
      * <code>null</code> and the property type cannot be inferred
      * element type will be <code>null</code>.
      * @return an observable list tracking the collection-typed named property
      * of the given bean object
      */
     public static IObservableList observeList(Realm realm, Object bean,
             String propertyName, Class elementType) {
         PropertyDescriptor propertyDescriptor = getPropertyDescriptor(bean
                 .getClass(), propertyName);
         elementType = getCollectionElementType(elementType, propertyDescriptor);

         return new JavaBeanObservableList(realm, bean, propertyDescriptor,
                 elementType);
     }

     /**
      * Returns an observable set in the given realm tracking the
      * collection-typed named property of the given bean object
      *
      * @param realm
      * the realm
      * @param bean
      * the bean object
      * @param propertyName
      * the name of the property
      * @return an observable set tracking the collection-typed named property of
      * the given bean object
      */
     public static IObservableSet observeSet(Realm realm, Object bean,
             String propertyName) {
         return observeSet(realm, bean, propertyName, null);
     }

     /**
      * Returns a factory for creating obervable values tracking the given
      * property of a particular bean object
      *
      * @param realm
      * the realm to use
      * @param propertyName
      * the name of the property
      * @return an observable value factory
      */
     public static IObservableFactory valueFactory(final Realm realm,
             final String propertyName) {
         return new IObservableFactory() {
             public IObservable createObservable(Object target) {
                 return observeValue(realm, target, propertyName);
             }
         };
     }

     /**
      * Returns a factory for creating obervable lists tracking the given
      * property of a particular bean object
      *
      * @param realm
      * the realm to use
      * @param propertyName
      * the name of the property
      * @param elementType
      * @return an observable list factory
      */
     public static IObservableFactory listFactory(final Realm realm,
             final String propertyName, final Class elementType) {
         return new IObservableFactory() {
             public IObservable createObservable(Object target) {
                 return observeList(realm, target, propertyName, elementType);
             }
         };
     }

     /**
      * Returns a factory for creating obervable sets tracking the given property
      * of a particular bean object
      *
      * @param realm
      * the realm to use
      * @param propertyName
      * the name of the property
      * @return an observable set factory
      */
     public static IObservableFactory setFactory(final Realm realm,
             final String propertyName) {
         return new IObservableFactory() {
             public IObservable createObservable(Object target) {
                 return observeSet(realm, target, propertyName);
             }
         };
     }

     /**
      * Helper method for
      * <code>MasterDetailObservables.detailValue(master, valueFactory(realm,
      propertyName), propertyType)</code>
      *
      * @param realm
      * @param master
      * @param propertyName
      * @param propertyType
      * can be <code>null</code>
      * @return an observable value that tracks the current value of the named
      * property for the current value of the master observable value
      *
      * @see MasterDetailObservables
      */
     public static IObservableValue observeDetailValue(Realm realm,
             IObservableValue master, String propertyName, Class propertyType) {

         IObservableValue value = MasterDetailObservables.detailValue(master,
                 valueFactory(realm, propertyName), propertyType);
         BeanObservableValueDecorator decorator = new BeanObservableValueDecorator(
                 value, master, getValueTypePropertyDescriptor(master,
                         propertyName));

         return decorator;
     }

     /**
      * Helper method for
      * <code>MasterDetailObservables.detailList(master, listFactory(realm,
      propertyName, propertyType), propertyType)</code>
      *
      * @param realm
      * @param master
      * @param propertyName
      * @param propertyType
      * can be <code>null</code>
      * @return an observable list that tracks the named property for the current
      * value of the master observable value
      *
      * @see MasterDetailObservables
      */
     public static IObservableList observeDetailList(Realm realm,
             IObservableValue master, String propertyName, Class propertyType) {
         IObservableList observableList = MasterDetailObservables.detailList(
                 master, listFactory(realm, propertyName, propertyType),
                 propertyType);
         BeanObservableListDecorator decorator = new BeanObservableListDecorator(
                 observableList, master, getValueTypePropertyDescriptor(master,
                         propertyName));

         return decorator;
     }

     /**
      * Helper method for
      * <code>MasterDetailObservables.detailSet(master, setFactory(realm,
      propertyName), propertyType)</code>
      *
      * @param realm
      * @param master
      * @param propertyName
      * @param propertyType
      * can be <code>null</code>
      * @return an observable set that tracks the named property for the current
      * value of the master observable value
      *
      * @see MasterDetailObservables
      */
     public static IObservableSet observeDetailSet(Realm realm,
             IObservableValue master, String propertyName, Class propertyType) {

         IObservableSet observableSet = MasterDetailObservables.detailSet(
                 master, setFactory(realm, propertyName, propertyType),
                 propertyType);
         BeanObservableSetDecorator decorator = new BeanObservableSetDecorator(
                 observableSet, master, getValueTypePropertyDescriptor(master,
                         propertyName));

         return decorator;
     }

     /**
      * @param realm
      * @param bean
      * @param propertyName
      * @param elementType
      * can be <code>null</code>
      * @return an observable set that tracks the current value of the named
      * property for given bean object
      */
     public static IObservableSet observeSet(Realm realm, Object bean,
             String propertyName, Class elementType) {
         PropertyDescriptor propertyDescriptor = getPropertyDescriptor(bean
                 .getClass(), propertyName);
         elementType = getCollectionElementType(elementType, propertyDescriptor);

         return new JavaBeanObservableSet(realm, bean, propertyDescriptor,
                 elementType);
     }

     /**
      * @param realm
      * @param propertyName
      * @param elementType
      * can be <code>null</code>
      * @return an observable set factory for creating observable sets
      */
     public static IObservableFactory setFactory(final Realm realm,
             final String propertyName, final Class elementType) {
         return new IObservableFactory() {
             public IObservable createObservable(Object target) {
                 return observeSet(realm, target, propertyName, elementType);
             }
         };
     }

     /**
      * @param elementType
      * can be <code>null</code>
      * @param propertyDescriptor
      * @return type of the items in a collection/array property
      */
     private static Class getCollectionElementType(Class elementType,
             PropertyDescriptor propertyDescriptor) {
         if (elementType == null) {
             Class propertyType = propertyDescriptor.getPropertyType();
             elementType = propertyType.isArray() ? propertyType
                     .getComponentType() : Object .class;
         }

         return elementType;
     }

     /**
      * @param observable
      * @param propertyName
      * @return property descriptor or <code>null</code>
      */
     private static PropertyDescriptor getValueTypePropertyDescriptor(
             IObservableValue observable, String propertyName) {
         return (observable.getValueType() != null) ? getPropertyDescriptor(
                 (Class ) observable.getValueType(), propertyName) : null;
     }
 }

